﻿namespace Microsoft.VisualStudio.TestTools.UnitTesting
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.IO;
    using System.Linq;
    using System.Windows.Forms;

    internal class AssemblyReferences
    {
        private static IDictionary<string, string> assemblyReferencePaths;

        #region Public Methods
        public static string GetReferenceFullPath(Type type, string reference)
        {
            EnsureAssemblyReferencePathsAreLoaded();

            var directoryName = AssemblyBuilderDirectoryAttribute.GetDirectoryName(type);

            string fullFileName;
            var referencePath = GetReferencePath(directoryName);
            if (referencePath == null)
            {
                fullFileName = GetFullReferenceFileNameFromUser(directoryName, reference);
                AssemblyReferences.assemblyReferencePaths.Add(directoryName, Path.GetDirectoryName(fullFileName));
                AssemblyReferences.WriteAssemblyReferencePaths();
            }
            else
            {
                fullFileName = Path.Combine(referencePath, reference);
                if (!File.Exists(fullFileName))
                {
                    throw new InvalidOperationException("Could not find '" + fullFileName + "'.");
                }
            }

            return fullFileName;
        }

        public static string GetReferencePath(string directoryName)
        {
            EnsureAssemblyReferencePathsAreLoaded();

            string referencePath;
            AssemblyReferences.assemblyReferencePaths.TryGetValue(directoryName, out referencePath);

            return referencePath;
        }
        #endregion Public Methods

        #region Private Methods
        private static void EnsureAssemblyReferencePathsAreLoaded()
        {
            if (AssemblyReferences.assemblyReferencePaths == null)
            {
                AssemblyReferences.assemblyReferencePaths = LoadAssemblyReferencePaths();
            }
        }

        private static string GetFullReferenceFileNameFromUser(string directoryName, string reference)
        {
            var bringToFrontForm = new Form
            {
                FormBorderStyle = FormBorderStyle.None,
                Size = new Size(1, 1),
                StartPosition = FormStartPosition.CenterScreen
            };
            using (bringToFrontForm)
            {
                bringToFrontForm.Show();
                bringToFrontForm.BringToFront();

                var openFileDialog = new OpenFileDialog();
                openFileDialog.CheckFileExists = true;
                openFileDialog.Multiselect = false;
                openFileDialog.RestoreDirectory = true;
                openFileDialog.Title = "Select the reference path for '" + directoryName + "'.";
                openFileDialog.FileName = reference;
                var dialogResult = openFileDialog.ShowDialog(bringToFrontForm);
                if (dialogResult != DialogResult.OK)
                {
                    throw new InvalidOperationException("Could not find file " + reference);
                }

                return Path.Combine(
                    Path.GetDirectoryName(openFileDialog.FileName),
                    reference);
            }
        }

        private static string BuildReferencePathsFileName()
        {
            var assemblyPath = Path.GetDirectoryName(typeof(AssemblyBuilder).Assembly.Location);

            return Path.Combine(assemblyPath, "AssemblyBuilder.dat");
        }

        private static IDictionary<string, string> LoadAssemblyReferencePaths()
        {
            var paths = new Dictionary<string, string>();

            // Load the file the holds the reference path information if it exists.
            var referencePathsFileName = BuildReferencePathsFileName();
            if (File.Exists(referencePathsFileName))
            {
                foreach (var line in File.ReadAllLines(referencePathsFileName))
                {
                    var parts = line.Split(',');
                    paths.Add(parts[0], parts[1]);
                }
            }

            return paths;
        }

        private static void WriteAssemblyReferencePaths()
        {
            var referencePathsFileName = BuildReferencePathsFileName();
            var pathLines =
                from entry in AssemblyReferences.assemblyReferencePaths
                select entry.Key + "," + entry.Value;
            File.WriteAllLines(referencePathsFileName, pathLines);
        }
        #endregion Private Methods
    }
}
